package org.zoomdev.zoom.dao.entity;

import org.zoomdev.zoom.aop.Constructor;
import org.zoomdev.zoom.aop.factory.ConstructorFactory;
import org.zoomdev.zoom.dao.SplitTableRule;
import org.zoomdev.zoom.dao.SqlConfig;
import org.zoomdev.zoom.dao.adapters.GenerateKeyAdapter;
import org.zoomdev.zoom.dao.meta.TableKeyMeta;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class Entity<T> {
    private final Map<String, Field2ColumnMappingConfig> cache = new ConcurrentHashMap<>();
    private Field2ColumnMappingConfig[] toDatabases;
    private Field2ColumnMappingConfig[] toFields;
    private TableSource table;
    private Constructor constructor;
    private Field2ColumnMappingConfig[] columnConfigs;
    private String[] generateKeys;
    private Class<T> type;
    private Field2ColumnMappingConfig splitColumn;
    private SplitTableRule splitTableRule;
    private Field2ColumnMappingConfig[] primaryKeys;
    private TableKeyMeta tableMeta;
    private GenerateKeyAdapter generateKeyAdapter;


    public T newInstance() {
        Constructor constructor = this.constructor;
        if (constructor == null) {
            synchronized (this) {
                constructor = this.constructor;
                if (constructor == null) {
                    constructor = ConstructorFactory.FACTORY.create(type);
                    this.constructor = constructor;
                }
            }
        }
        return (T) constructor.newInstance();
    }

    public Field2ColumnMappingConfig[] getToFields() {
        Field2ColumnMappingConfig[] toFields = this.toFields;
        if (toFields == null) {
            synchronized (this) {
                toFields = this.toFields;
                if (toFields == null) {
                    toFields = EntityFieldBuilder.buildToFields(type, getColumnConfigs());
                    this.toFields = toFields;
                }
            }
        }
        return toFields;
    }

    public Field2ColumnMappingConfig[] getColumnConfigs() {
        return columnConfigs;
    }

    public void setColumnConfigs(Field2ColumnMappingConfig[] columnConfigs) {
        this.columnConfigs = columnConfigs;
    }


    public Field2ColumnMappingConfig[] getToDatabases() {
        Field2ColumnMappingConfig[] toDatabases = this.toDatabases;
        if (toDatabases == null) {
            synchronized (this) {
                toDatabases = this.toDatabases;
                if (toDatabases == null) {
                    toDatabases = EntityFieldBuilder.buildToDatabases(type, tableMeta, columnConfigs, this);
                    this.toDatabases = toDatabases;
                }
            }
        }
        return toDatabases;
    }

    public Field2ColumnMappingConfig getConfig(String name) {
        return cache.computeIfAbsent(name, k -> Field2ColumnMappingConfig.find(columnConfigs, k));
    }

    public PreparedStatement prepareInsert(Connection conn, String sql) throws SQLException {
        String[] generateKeys = this.generateKeys;
        if (generateKeys != null) {
            return conn.prepareStatement(sql, generateKeys);
        }
        return conn.prepareStatement(sql);
    }

    public void afterInsert(SqlConfig config, Object data, PreparedStatement stmt) throws SQLException {
        try (ResultSet rs = stmt.getGeneratedKeys()) {
            if (rs.next()) {
                generateKeyAdapter.handle(config, data, rs);
            }
        }
    }

    public TableSource getTable() {
        return table;
    }

    public void setTable(TableSource table) {
        this.table = table;
    }

    public String getTableName() {
        return table.getTable();
    }

    public Constructor getConstructor() {
        return constructor;
    }

    public void setConstructor(Constructor constructor) {
        this.constructor = constructor;
    }

    public String[] getGenerateKeys() {
        return generateKeys;
    }

    public void setGenerateKeys(String[] generateKeys) {
        this.generateKeys = generateKeys;
    }

    public Class<T> getType() {
        return type;
    }

    public void setType(Class<T> type) {
        this.type = type;
    }

    public SplitTableRule getSplitTableRule() {
        return splitTableRule;
    }

    public void setSplitTableRule(SplitTableRule splitTableRule) {
        this.splitTableRule = splitTableRule;
    }

    public Field2ColumnMappingConfig getSplitColumn() {
        return splitColumn;
    }

    public void setSplitColumn(Field2ColumnMappingConfig splitColumn) {
        this.splitColumn = splitColumn;
    }

    public Field2ColumnMappingConfig[] getPrimaryKeys() {
        return primaryKeys;
    }

    public void setPrimaryKeys(Field2ColumnMappingConfig[] primaryKeys) {
        this.primaryKeys = primaryKeys;
    }

    public void setTableMeta(TableKeyMeta tableMeta) {
        this.tableMeta = tableMeta;
    }

    public void setGenerateKeyAdapter(GenerateKeyAdapter generateKeyAdapter) {
        this.generateKeyAdapter = generateKeyAdapter;
    }
}
